﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Reflection;

namespace MvcPatch
{
    internal class ControllerTypeCache
    {
        private IBuildManager m_buildManager;
        public IBuildManager BuildManager
        {
            get
            {
                if (m_buildManager == null)
                {
                    m_buildManager = new BuildManagerWrapper();
                }

                return m_buildManager;
            }
            set
            {
                m_buildManager = value;
            }
        }

        private object m_mutex = new object();
        private Dictionary<string, Dictionary<string, Type>> m_cache;

        public Type Get(string area, string controllerName)
        {
            this.EnsureInitialized();

            Dictionary<string, Type> areaCache;
            if (!this.m_cache.TryGetValue(area.ToLowerInvariant(), out areaCache)) return null;

            Type type;
            if (!areaCache.TryGetValue(controllerName.ToLowerInvariant(), out type)) return null;

            return type;
        }

        private void EnsureInitialized()
        {
            if (this.m_cache == null)
            {
                lock (m_mutex)
                {
                    if (this.m_cache == null)
                    {
                        this.m_cache = Initialize(this.BuildManager);
                    }
                }
            }
        }

        private static Dictionary<string, Dictionary<string, Type>> Initialize(IBuildManager buildManager)
        {
            var controllerTypes = new List<Type>();

            foreach (Assembly assembly in buildManager.GetReferencedAssemblies())
            {
                Type[] typesInAsm;
                try
                {
                    typesInAsm = assembly.GetTypes();
                }
                catch (ReflectionTypeLoadException ex)
                {
                    typesInAsm = ex.Types;
                }

                controllerTypes.AddRange(typesInAsm.Where(IsControllerType));
            }

            return controllerTypes
                .GroupBy(t => AreaHelper.GetArea(t).ToLowerInvariant())
                .ToDictionary(
                    g => g.Key,
                    g => g.ToDictionary(
                        t => GetControllerName(t).ToLowerInvariant()));
        }

        private static string GetControllerName(Type t)
        {
            return t.Name.Substring(0, t.Name.Length - "Controller".Length);
        }

        private static bool IsControllerType(Type t)
        {
            return
                t != null &&
                t.IsPublic &&
                t.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) &&
                !t.IsAbstract &&
                typeof(IController).IsAssignableFrom(t);
        }
    }
}
